// 版权所有2009 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// 垃圾收集器：类型和堆位图。
// 
// 堆栈、数据和bss位图
// 
// 数据和bss部分中的堆栈帧和全局变量是
// 由位图描述，每个指针大小的字有1位。“1”位
// 表示该单词是GC访问的活动指针（称为“指针”）。“0”位表示GC 
// 应忽略该单词（称为“标量”，但它可能是一个死指针值）。
// 
// 堆位图
// 
// 堆位图包含堆中每个指针大小的字的2位，
// 存储在支持每个堆的heapArena元数据中。
// 也就是说，如果ha是竞技场开始的最佳人选，
// 那么ha。位图[0]保存四个单词start 
// 到start+3*ptrSize，ha的2位条目。位图[1]保存
// start+4*ptrSize到start+7*ptrSize的条目，依此类推。
// 
// 在每个2位条目中，较低的位是指针/标量位，仅为
// 与上述堆栈/数据位图类似。高位
// 表示扫描/失效：“1”值（“扫描”）表示可能有
// 在分配的后面的字中有指针，“0”值
// （“失效”）表示分配中没有更多指针。如果
// 高位为0，低位也必须为0，此
// 表示扫描可以忽略分配的其余部分。
// 
// 写入字节时，2位条目被拆分，因此字节的上半部分
// 包含4个高位（扫描）位，下半部分包含4个低位
// （指针）位。此表单允许从1位复制到4位，再复制到
// 保持指针位连续，而不必将它们隔开。
// 
// 代码利用了堆的零值
// 位图表示标量/死亡。
// 修改编码时必须保留此属性。
// 
// 不维护noscan跨度的位图。代码必须确保
// 在通过
// 检查跨度中的noscan位或通过查看其
// 类型信息来查看其位图之前，对象是可扫描的。

package runtime

import (
	"internal/goarch"
	"runtime/internal/atomic"
	"runtime/internal/sys"
	"unsafe"
)

const (
	bitPointer = 1 << 0
	bitScan    = 1 << 4

	heapBitsShift      = 1     // 连续位指针或位扫描项之间的移位偏移量
	wordsPerBitmapByte = 8 / 2 // 一个位图字节描述的堆字

	// 一个字节中的所有扫描位/指针位
	bitScanAll    = bitScan | bitScan<<heapBitsShift | bitScan<<(2*heapBitsShift) | bitScan<<(3*heapBitsShift)
	bitPointerAll = bitPointer | bitPointer<<heapBitsShift | bitPointer<<(2*heapBitsShift) | bitPointer<<(3*heapBitsShift)
)

// addb返回字节指针p+n。
// go:nowritebarrier 
// go:nosplit 
func addb(p *byte, n uintptr) *byte {
	// 注意：写出完整表达式而不是调用add（p，n）
	// 减少在内联过程中由
	// 编译器为这个微不足道的表达式生成的临时变量的数量。
	return (*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(p)) + n))
}

// subtractb返回字节指针p-n.
// go:nowritebarrier 
// go:nosplit 
func subtractb(p *byte, n uintptr) *byte {
	// 注意：写出完整表达式，而不是调用add（p，-n）
	// 以减少
	// 编译器在内联期间为这个微不足道的表达式生成的临时数。
	return (*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(p)) - n))
}

// add1返回字节指针p+1。
// go:nowritebarrier 
// go:nosplit 
func add1(p *byte) *byte {
	// 注意：编写完整表达式，而不是调用addb（p，1）
	// 以减少
	// 编译器在内联期间为这个微不足道的表达式生成的临时数。
	return (*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(p)) + 1))
}

// subtract1返回字节指针p-1。
// go:nowritebarrier 
// 
// nosplit，因为它在写屏障期间使用，不能被抢占。
// go:nosplit 
func subtract1(p *byte) *byte {
	// 注意：编写了完整的表达式，而不是调用subtractb（p，1）
	// 以减少在内联期间由
	// 编译器为这个微不足道的表达式生成的临时数。
	return (*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(p)) - 1))
}

// heapBits提供对单个堆字的位图位的访问。
// heapBits上的方法采用值接收器，因此编译器
// 可以更轻松地内联调用这些方法，并独立注册
// 结构字段。
type heapBits struct {
	bitp  *uint8
	shift uint32
	arena uint32 // 包含bitp的堆竞技场索引
	last  *uint8 // 最后一个字节竞技场的位图
}

// 让编译器检查heapBits。竞技场足够容纳
// 竞技场的最大帧数。
var _ = heapBits{arena: (1<<heapAddrBits)/heapArenaBytes - 1}

// markBits提供对堆中对象的标记位的访问。
// bytep指向保存标记位的字节。
// 掩码是一个设置了单个位的字节，可以通过*bytep 
// 来查看该位是否已设置。
// /*m.byte和m.mask！=0表示标记位已设置。
// 索引可与跨度信息一起用于生成
// 堆中对象的地址。
// 我们维护一组标记位用于分配，另一组用于
// 标记。
type markBits struct {
	bytep *uint8
	mask  uint8
	index uintptr
}

// go:nosplit 
func (s *mspan) allocBitsForIndex(allocBitIndex uintptr) markBits {
	bytep, mask := s.allocBits.bitp(allocBitIndex)
	return markBits{bytep, mask, allocBitIndex}
}

// refillAllocCache从字节
// 开始计算8个字节的s.allocBits，并将其取反，以便可以使用ctz（计数尾随零）指令
// 。然后将这8个字节放入缓存的64位
// s.allocache。
func (s *mspan) refillAllocCache(whichByte uintptr) {
	bytes := (*[8]uint8)(unsafe.Pointer(s.allocBits.bytep(whichByte)))
	aCache := uint64(0)
	aCache |= uint64(bytes[0])
	aCache |= uint64(bytes[1]) << (1 * 8)
	aCache |= uint64(bytes[2]) << (2 * 8)
	aCache |= uint64(bytes[3]) << (3 * 8)
	aCache |= uint64(bytes[4]) << (4 * 8)
	aCache |= uint64(bytes[5]) << (5 * 8)
	aCache |= uint64(bytes[6]) << (6 * 8)
	aCache |= uint64(bytes[7]) << (7 * 8)
	s.allocCache = ^aCache
}

// nextFreeIndex返回s中位于
// 或s.freeindex之后的下一个自由对象的索引。
// 如果评测允许的话，可以使用一些硬件指令来提高这个
// 的速度。
func (s *mspan) nextFreeIndex() uintptr {
	sfreeindex := s.freeindex
	snelems := s.nelems
	if sfreeindex == snelems {
		return sfreeindex
	}
	if sfreeindex > snelems {
		throw("s.freeindex > s.nelems")
	}

	aCache := s.allocCache

	bitIndex := sys.Ctz64(aCache)
	for bitIndex == 64 {
		// 将索引移动到下一个缓存位的开始处。
		sfreeindex = (sfreeindex + 64) &^ (64 - 1)
		if sfreeindex >= snelems {
			s.freeindex = snelems
			return snelems
		}
		whichByte := sfreeindex / 8
		// 用接下来的64个alloc位重新填充s.allocCache。
		s.refillAllocCache(whichByte)
		aCache = s.allocCache
		bitIndex = sys.Ctz64(aCache)
		// 缓存位中没有可用的内容
		// 获取接下来的8个字节，然后重试。
	}
	result := sfreeindex + uintptr(bitIndex)
	if result >= snelems {
		s.freeindex = snelems
		return snelems
	}

	s.allocCache >>= uint(bitIndex + 1)
	sfreeindex = result + 1

	if sfreeindex%64 == 0 && sfreeindex != snelems {
		// 我们刚刚增加了s.freeindex，所以它不是0。
		// 由于s.allocache中的每个1都被遇到并用于分配
		// 它被移开了。此时，s.allocache包含所有0。
		// 重新填充s.allocache，使其对应于s.allocBits处从s.freeindex开始的位。
		whichByte := sfreeindex / 8
		s.refillAllocCache(whichByte)
	}
	s.freeindex = sfreeindex
	return result
}

// isFree报告s中索引的第四个对象是否未分配。
// 
// 调用者必须确保s.state是mSpanInUse，并且必须有
// 自确保这一点以来，没有抢占点（这可能允许
// GC转换，这将允许状态更改）。
func (s *mspan) isFree(index uintptr) bool {
	if index < s.freeindex {
		return false
	}
	bytep, mask := s.allocBits.bitp(index)
	return *bytep&mask == 0
}

// divideByElemSize返回n/s.elemsize。
// n必须在[0，s.npages*_PageSize），
// 或者可能正好是s.npages*_pagesizeabcdefg
func (s *mspan) divideByElemSize(n uintptr) uintptr {
	const doubleCheck = false

	// 请参阅mksizelasses.go的computeDivMagic中的解释。
	q := uintptr((uint64(n) * uint64(s.divMul)) >> 32)

	if doubleCheck && q != n/s.elemsize {
		println(n, "/", s.elemsize, "should be", n/s.elemsize, "but got", q)
		throw("bad magic division")
	}
	return q
}

func (s *mspan) objIndex(p uintptr) uintptr {
	return s.divideByElemSize(p - s.base())
}

func markBitsForAddr(p uintptr) markBits {
	s := spanOf(p)
	objIndex := s.objIndex(p)
	return s.markBitsForIndex(objIndex)
}

func (s *mspan) markBitsForIndex(objIndex uintptr) markBits {
	bytep, mask := s.gcmarkBits.bitp(objIndex)
	return markBits{bytep, mask, objIndex}
}

func (s *mspan) markBitsForBase() markBits {
	return markBits{(*uint8)(s.gcmarkBits), uint8(1), 0}
}

// isMarked报告是否设置了标记位m。
func (m markBits) isMarked() bool {
	return *m.bytep&m.mask != 0
}

// setMarked按原子顺序设置标记位。
func (m markBits) setMarked() {
	// 我们过去很聪明，在某些
	// 情况下使用非原子更新，但不值得冒险。
	atomic.Or8(m.bytep, m.mask)
}

// setMarkedNonAtomic以非原子方式设置标记位中的标记位。
func (m markBits) setMarkedNonAtomic() {
	*m.bytep |= m.mask
}

// clearMarked以原子方式清除标记位中的标记位。
func (m markBits) clearMarked() {
	// 可能正在与其他更新竞争，所以请始终使用原子更新。
	// 我们过去很聪明，在某些
	// 情况下使用非原子更新，但这不值得冒险。
	atomic.And8(m.bytep, ^m.mask)
}

// markBitsForSpan返回span基地址的markBits。
func markBitsForSpan(base uintptr) (mbits markBits) {
	mbits = markBitsForAddr(base)
	if mbits.mask != 1 {
		throw("markBitsForSpan: unaligned start")
	}
	return mbits
}

// advance将标记位前进到范围中的下一个对象。
func (m *markBits) advance() {
	if m.mask == 1<<7 {
		m.bytep = (*uint8)(unsafe.Pointer(uintptr(unsafe.Pointer(m.bytep)) + 1))
		m.mask = 1
	} else {
		m.mask = m.mask << 1
	}
	m.index++
}

// heapBitsForAddr返回地址addr的heapBits。
// 调用者必须确保addr位于分配的范围内。
// 尤其要注意不要指向物体的末端。
// 
// nosplit，因为它在写屏障期间使用，不能被抢占。
// go:nosplit 
func heapBitsForAddr(addr uintptr) (h heapBits) {
	// /每个字2位，每个字节4对，掩码是硬编码的。
	arena := arenaIndex(addr)
	ha := mheap_.arenas[arena.l1()][arena.l2()]
	// 编译器使用一个负载来检查ha是否为零，但在这个
	// 情况下，我们几乎再也不会命中缓存线，所以
	// 进行值检查更有意义。shuan chuang jian defg
	if ha == nil {
		// 我们希望调用程序崩溃。
		return
	}
	h.bitp = &ha.bitmap[(addr/(goarch.PtrSize*4))%heapArenaBitmapBytes]
	h.shift = uint32((addr / goarch.PtrSize) & 3)
	h.arena = uint32(arena)
	h.last = &ha.bitmap[len(ha.bitmap)-1]
	return
}

// clobberdeadPtr是一个特殊值，当设置-clobberdead标志时，编译器使用该值来设置
// clobber死堆栈插槽。
const clobberdeadPtr = uintptr(0xdeaddead | 0xdeaddead<<((^uintptr(0)>>63)*32))

// 坏指针在堆恐慌中抛出坏指针。
func badPointer(s *mspan, p, refBase, refOff uintptr) {
	// 通常这表示不正确地使用不安全或cgo的
	// 在Go堆中存储错误指针。它还可能表示运行时
	// 错误。
	// 
	// TODO（奥斯汀）：我们可以更积极地
	// 在分配的范围内检测指向未分配对象的指针
	// 。
	printlock()
	print("runtime: pointer ", hex(p))
	if s != nil {
		state := s.state.get()
		if state != mSpanInUse {
			print(" to unallocated span")
		} else {
			print(" to unused region of span")
		}
		print(" span.base()=", hex(s.base()), " span.limit=", hex(s.limit), " span.state=", state)
	}
	print("\n")
	if refBase != 0 {
		print("runtime: found in object at *(", hex(refBase), "+", hex(refOff), ")\n")
		gcDumpObject("object", refBase, refOff)
	}
	getg().m.traceback = 2
	throw("found bad pointer in Go heap (incorrect use of unsafe or cgo?)")
}

// findObject返回包含
// 地址p、对象的跨度和s.
// 如果p不指向堆对象，则返回base==0。
// 
// 如果p点是无效的堆指针，则进行调试。无效的0，
// findObject恐慌。
// 
// refBase和refOff可选地给出在其中找到指针p的对象
// 的基址，以及在其中找到指针p的字节偏移量
// 。这些用于错误报告。
// 
// 它是nosplit，因此p作为指向当前goroutine堆栈的指针是安全的。cun chu defg
// go:nosplit 
func findObject(p, refBase, refOff uintptr) (base uintptr, s *mspan, objIndex uintptr) {
	s = spanOf(p)
	// 如果s为零，则虚拟地址从来都不是堆的一部分。
	// 这个指针可能指向某个mmap'd区域，所以我们允许它。
	if s == nil {
		if (GOARCH == "amd64" || GOARCH == "arm64") && p == clobberdeadPtr && debug.invalidptr != 0 {
			// 如果看到clobberdeadPtr，则崩溃。目前只在AMD64和ARM64上使用
			// 因为它们是唯一实现编译器clobberdead模式的平台。在这些平台上，clobberdeadPtr不能是有效地址。
			badPointer(s, p, refBase, refOff)
		}
		return
	}
	// 如果p是一个坏指针，它可能不在s的范围内。
	// 
	// 检查s.state以与span初始化同步
	// 然后再检查其他字段。另见spanOfHeap。
	if state := s.state.get(); state != mSpanInUse || p < s.base() || p >= s.limit {
		// 指向堆栈的指针也可以，运行时显式地管理这些指针。
		if state == mSpanManual {
			return
		}
		// 以下内容确保我们对数据
		// 结构中包含有效指针的内容严格要求。
		if debug.invalidptr != 0 {
			badPointer(s, p, refBase, refOff)
		}
		return
	}

	objIndex = s.objIndex(p)
	base = s.base() + objIndex*s.elemsize
	return
}

// verifyNotInHeapPtr报告是否将堆内指针转换为不安全的指针。指针没问题。
// go:linkname reflect\u verifynotinheaptr reflect。verifyNotInHeapPtr 
func reflect_verifyNotInHeapPtr(p uintptr) bool {
	// 只要上面的findObject不调用badPointer，就可以转换为指针。
	// 既然我们已经承诺p不会指向堆，那么就不允许堆
	// 指针和特殊的阻塞指针。
	return spanOf(p) == nil && p != clobberdeadPtr
}

// next返回描述内存中下一个指针大小的字的堆位。
// 也就是说，如果h描述地址p，则h.next（）描述p+ptrSize。
// 注意next不修改h。调用者必须记录结果。
// 
// nosplit，因为它在写屏障期间使用，不能被抢占。
// 开始：不分裂
func (h heapBits) next() heapBits {
	if h.shift < 3*heapBitsShift {
		h.shift += heapBitsShift
	} else if h.bitp != h.last {
		h.bitp, h.shift = add1(h.bitp), 0
	} else {
		// 移动到下一个竞技场。
		return h.nextArena()
	}
	return h
}

// nextArena进入下一个堆竞技场的开始。
// 
// 这是一个通往下一步的慢路径助手。gc的内联程序知道
// heapBits。next可以内联，尽管它称之为。这是
// 标记为noinline，因此它不会内联到next，并导致next 
// 太大而无法内联。
// 
// go:nosplit 
// go:noinline 
func (h heapBits) nextArena() heapBits {
	h.arena++
	ai := arenaIdx(h.arena)
	l2 := mheap_.arenas[ai.l1()]
	if l2 == nil {
		// 我们刚刚通过了对象的结尾，它也是堆的结尾。毒药h.它
		// 在这一点上永远不应该被取消引用。
		return heapBits{}
	}
	ha := l2[ai.l2()]
	if ha == nil {
		return heapBits{}
	}
	h.bitp, h.shift = &ha.bitmap[0], 0
	h.last = &ha.bitmap[len(ha.bitmap)-1]
	return h
}

// forward返回在内存中h之前描述n个指针大小的单词的heapBits。
// 也就是说，如果h描述地址p，则h.forward（n）描述地址p+n*ptrSize。
// h.forward（1）相当于h.next（），只是速度较慢。
// 注意forward不修改h。调用者必须记录结果。
// bits返回当前字的堆位。
// go:nosplit 
func (h heapBits) forward(n uintptr) heapBits {
	n += uintptr(h.shift) / heapBitsShift
	nbitp := uintptr(unsafe.Pointer(h.bitp)) + n/4
	h.shift = uint32(n%4) * heapBitsShift
	if nbitp <= uintptr(unsafe.Pointer(h.last)) {
		h.bitp = (*uint8)(unsafe.Pointer(nbitp))
		return h
	}

	// 我们进入了一个新的堆竞技场。
	past := nbitp - (uintptr(unsafe.Pointer(h.last)) + 1)
	h.arena += 1 + uint32(past/heapArenaBitmapBytes)
	ai := arenaIdx(h.arena)
	if l2 := mheap_.arenas[ai.l1()]; l2 != nil && l2[ai.l2()] != nil {
		a := l2[ai.l2()]
		h.bitp = &a.bitmap[past%heapArenaBitmapBytes]
		h.last = &a.bitmap[len(a.bitmap)-1]
	} else {
		h.bitp, h.last = nil, nil
	}
	return h
}

// forwardOrBoundary类似于forward，但在位图连续部分之间的边界处停止。它返回前进的字数
// 将<=n。
func (h heapBits) forwardOrBoundary(n uintptr) (heapBits, uintptr) {
	maxn := 4 * ((uintptr(unsafe.Pointer(h.last)) + 1) - uintptr(unsafe.Pointer(h.bitp)))
	if n > maxn {
		n = maxn
	}
	return h.forward(n), n
}

// 调用者可以通过位扫描和位指针&-ing测试更多指针和isPointer。
// 结果的高位包含后续字
// 的位，这些位由相同的位图字节描述。
// 
// nosplit，因为它在写屏障期间使用，不能被抢占。
// go:nosplit 
func (h heapBits) bits() uint32 {
	// （shift&31）从生成的代码中删除测试和条件分支
	// 。
	return uint32(*h.bitp) >> (h.shift & 31)
}

// morePointers报告这个单词和这个对象中的所有剩余单词
// 是否是标量。
// h不得描述对象的第二个单词。
func (h heapBits) morePointers() bool {
	return h.bits()&bitScan != 0
}

// isPointer报告堆位是否描述指针字。
// 
// nosplit，因为它在写屏障期间使用，不能被抢占。
// go:nosplit 
func (h heapBits) isPointer() bool {
	return h.bits()&bitPointer != 0
}

// bulkBarrierPreWrite对内存范围[src，src+size]中的每个指针插槽执行写屏障
// 使用[dst，dst+size]。这将执行memmove之前所需的写屏障。
// src，dst和size必须与指针对齐。
// 范围[dst，dst+size）必须位于一个对象内。
// 它不执行实际写入操作。
// 
// 作为一种特殊情况，src==0表示这是用于
// memclr。对于每次写入
// barrier在
// 调用memmove（dst、src、size）之前立即预写。此函数标记为nosplit 
// 以避免被抢占；GC不得阻止memmove和执行屏障之间的goroutine 
// 。
// 如果此
// 可能正在将Go指针写入非Go内存，则调用方还负责cgo指针检查。
// 
// 对于包含
// 完全没有指针的分配，不维护指针位图；bulkBarrierPreWrite的任何调用方都必须首先通过检查typ来确保基础分配包含指针，通常是
// 。ptrdata。
// 
// 调用方必须执行cgo检查，如果是写载波。cgo。
// 
// go:nosplit 
func bulkBarrierPreWrite(dst, src, size uintptr) {
	if (dst|src|size)&(goarch.PtrSize-1) != 0 {
		throw("bulkBarrierPreWrite: unaligned arguments")
	}
	if !writeBarrier.needed {
		return
	}
	if s := spanOf(dst); s == nil {
		// 如果dst是全局的，请使用数据或BSS位图来
		// 执行写屏障。
		for _, datap := range activeModules() {
			if datap.data <= dst && dst < datap.edata {
				bulkBarrierBitmap(dst, src, size, dst-datap.data, datap.gcdatamask.bytedata)
				return
			}
		}
		for _, datap := range activeModules() {
			if datap.bss <= dst && dst < datap.ebss {
				bulkBarrierBitmap(dst, src, size, dst-datap.bss, datap.gcbssmask.bytedata)
				return
			}
		}
		return
	} else if s.state.get() != mSpanInUse || dst < s.base() || s.limit <= dst {
		// dst在某个时候是堆内存，但现在不是。
		// 它不可能是一个全球性的。它必须是我们的堆栈
		// 或者在直接通道发送的情况下，它可以是
		// 另一个堆栈。不管怎样，都不需要设置障碍。
		// 如果dst处于释放范围内，这也会被捕获，尽管这永远不会发生。
		return
	}

	buf := &getg().m.p.ptr().wbBuf
	h := heapBitsForAddr(dst)
	if src == 0 {
		for i := uintptr(0); i < size; i += goarch.PtrSize {
			if h.isPointer() {
				dstx := (*uintptr)(unsafe.Pointer(dst + i))
				if !buf.putFast(*dstx, 0) {
					wbBufFlush(nil, 0)
				}
			}
			h = h.next()
		}
	} else {
		for i := uintptr(0); i < size; i += goarch.PtrSize {
			if h.isPointer() {
				dstx := (*uintptr)(unsafe.Pointer(dst + i))
				srcx := (*uintptr)(unsafe.Pointer(src + i))
				if !buf.putFast(*dstx, *srcx) {
					wbBufFlush(nil, 0)
				}
			}
			h = h.next()
		}
	}
}

// bulkbarrierprewritesrc仅与bulkBarrierPreWrite类似，但
// 不为[dst，dst+大小]执行写屏障。
// 
// 除了bulkBarrierPreWrite 
// 调用方需要确保[dst，dst+size]已归零。
// 
// 这用于特殊情况，例如dst只是
// 用malloc创建并归零。
// go:nosplit 
func bulkBarrierPreWriteSrcOnly(dst, src, size uintptr) {
	if (dst|src|size)&(goarch.PtrSize-1) != 0 {
		throw("bulkBarrierPreWrite: unaligned arguments")
	}
	if !writeBarrier.needed {
		return
	}
	buf := &getg().m.p.ptr().wbBuf
	h := heapBitsForAddr(dst)
	for i := uintptr(0); i < size; i += goarch.PtrSize {
		if h.isPointer() {
			srcx := (*uintptr)(unsafe.Pointer(src + i))
			if !buf.putFast(0, *srcx) {
				wbBufFlush(nil, 0)
			}
		}
		h = h.next()
	}
}

// bulkBarrierBitmap执行写屏障，用于从[src，
// src+size）复制到[dst，dst+size）使用1位指针位图。src是
// 假定将maskOffset字节启动到
// 位图所覆盖的数据中（可能不是8的倍数）.
// 
// 这是由BulkBarrierWrite用于写入数据和BSS。
// 
// go:nosplit 
func bulkBarrierBitmap(dst, src, size, maskOffset uintptr, bits *uint8) {
	word := maskOffset / goarch.PtrSize
	bits = addb(bits, word/8)
	mask := uint8(1) << (word % 8)

	buf := &getg().m.p.ptr().wbBuf
	for i := uintptr(0); i < size; i += goarch.PtrSize {
		if mask == 0 {
			bits = addb(bits, 1)
			if *bits == 0 {
				// 跳过8个单词。
				i += 7 * goarch.PtrSize
				continue
			}
			mask = 1
		}
		if *bits&mask != 0 {
			dstx := (*uintptr)(unsafe.Pointer(dst + i))
			if src == 0 {
				if !buf.putFast(*dstx, 0) {
					wbBufFlush(nil, 0)
				}
			} else {
				srcx := (*uintptr)(unsafe.Pointer(src + i))
				if !buf.putFast(*dstx, *srcx) {
					wbBufFlush(nil, 0)
				}
			}
		}
		mask <<= 1
	}
}

// typeBitsBulkBarrier为每个
// 指针执行一个写屏障，该指针将由memmove使用类型位图定位这些
// dst+size]）。
// 指针槽，从[src，src+size]复制到[dst，
// 
// 类型类型必须与[src，src+size]完全对应[dst，dst+size]。
// dst，src和size必须是指针对齐的。
// 类型typ必须有一个普通位图，而不是GC程序。
// 此函数的唯一用途是在通道发送中，
// 64 kB通道元素限制为我们解决了这个问题。
// 
// 不能被抢占，因为它通常运行就在memmove之前，
// GC必须将其作为原子行为进行观察。
// 
// 调用方必须执行cgo检查，如果是写载波。cgo。
// 
// go:nosplit 
func typeBitsBulkBarrier(typ *_type, dst, src, size uintptr) {
	if typ == nil {
		throw("runtime: typeBitsBulkBarrier without type")
	}
	if typ.size != size {
		println("runtime: typeBitsBulkBarrier with type ", typ.string(), " of size ", typ.size, " but memory size", size)
		throw("runtime: invalid typeBitsBulkBarrier")
	}
	if typ.kind&kindGCProg != 0 {
		println("runtime: typeBitsBulkBarrier with type ", typ.string(), " with GC prog")
		throw("runtime: invalid typeBitsBulkBarrier")
	}
	if !writeBarrier.needed {
		return
	}
	ptrmask := typ.gcdata
	buf := &getg().m.p.ptr().wbBuf
	var bits uint32
	for i := uintptr(0); i < typ.ptrdata; i += goarch.PtrSize {
		if i&(goarch.PtrSize*8-1) == 0 {
			bits = uint32(*ptrmask)
			ptrmask = addb(ptrmask, 1)
		} else {
			bits = bits >> 1
		}
		if bits&1 != 0 {
			dstx := (*uintptr)(unsafe.Pointer(dst + i))
			srcx := (*uintptr)(unsafe.Pointer(src + i))
			if !buf.putFast(*dstx, *srcx) {
				wbBufFlush(nil, 0)
			}
		}
	}
}

// 对跨距进行操作的方法都要求h已由heapBitsForSpan返回
// 该大小n，total是mspan的布局方法返回的跨距布局描述
// 。
// 如果total>size*n，则表示跨度中有多余的剩余内存，
// 通常是因为四舍五入。
// 
// TODO（rsc）：可能会引入不同的heapBitsSpan类型。

// initSpan初始化span的堆位图。
// 如果这是一个指针大小的对象范围，它会将所有
// 字初始化为指针/扫描。
// 否则，它将所有字初始化为标量/死。
func (h heapBits) initSpan(s *mspan) {
	// 清除对象对应的位。
	nw := (s.npages << _PageShift) / goarch.PtrSize
	if nw%wordsPerBitmapByte != 0 {
		throw("initSpan: unaligned length")
	}
	if h.shift != 0 {
		throw("initSpan: unaligned base")
	}
	isPtrs := goarch.PtrSize == 8 && s.elemsize == goarch.PtrSize
	for nw > 0 {
		hNext, anw := h.forwardOrBoundary(nw)
		nbyte := anw / wordsPerBitmapByte
		if isPtrs {
			bitp := h.bitp
			for i := uintptr(0); i < nbyte; i++ {
				*bitp = bitPointerAll | bitScanAll
				bitp = add1(bitp)
			}
		} else {
			memclrNoHeapPointers(unsafe.Pointer(h.bitp), nbyte)
		}
		h = hNext
		nw -= anw
	}
}

// countAlloc返回通过
// 扫描分配位图在span s中分配的对象数。
func (s *mspan) countAlloc() int {
	count := 0
	bytes := divRoundUp(s.nelems, 8)
	// 迭代每个8字节的块，并用一个内在值计算分配
	// 的数量。请注意，newMarkBits保证
	// gcmarkBits将是8字节对齐的，因此我们不必担心边缘情况，不相关的位只会是零。
	for i := uintptr(0); i < bytes; i += 8 {
		// 从字节指针中提取64位，并获得一个OneScont。注意这里不安全的演员阵容并不能保持endianness，但这没关系。我们只关心有多少位是1，而不关心我们发现它们的顺序。
		mrkBits := *(*uint64)(unsafe.Pointer(s.gcmarkBits.bytep(i)))
		count += sys.OnesCount64(mrkBits)
	}
	return count
}

// HEAPBITSETTYPE记录新分配[x，x+大小）
// 
// 如果数据大小<大小，碎片[x+dataSize，x+size）是
// 记录为非指针数据。
// 已知该类型在某处有指针；
// malloc在没有指针时不调用heapBitsSetType，
// 因为在
// HeapBitsSweep.
// 给定范围内的一次分配活动，
// 并且范围内的位图始终位于字节边界上，
// 因此不存在访问堆位图的写-写竞争。
// 因此，heapBitsSetType可以在没有原子的情况下访问位图。
// 
// 在heapBitsSetType和像scanobject一样读取堆位图的东西
// 之间可能存在读写竞争。但是，由于
// heapBitsSetType仅用于尚未使
// 可访问的对象，因此读取器将忽略此
// 函数修改的位。这意味着该函数不能暂时修改属于相邻对象的
// 位。此外，在弱序
// 机器上，调用方必须在调用此函数和使对象可访问之间执行存储/存储（发布）屏障
// 。
func heapBitsSetType(x, size, dataSize uintptr, typ *_type) {
	const doubleCheck = false // 缓慢但有帮助；启用测试对此代码的修改

	const (
		mask1 = bitPointer | bitScan                        // 00010001 
		mask2 = bitPointer | bitScan | mask1<<heapBitsShift // 00110011 
		mask3 = bitPointer | bitScan | mask2<<heapBitsShift // 01101111 
	)

	// dataSize始终向上舍入到下一个malloc大小类
	// 除非分配延迟块，在这种情况下，
	// 大小为sizeof（_defer{}）（至少6个单词），数据大小可以任意大。
	// 
	// 检查大小==goarch。PtrSize和size==2*goarch。因此，PtrSize可以
	// 假设dataSize==size，而无需显式检查。

	if goarch.PtrSize == 8 && size == goarch.PtrSize {
		// 这是一个单词，它有指针，它一定是一个指针。
		// 由于所有分配的单字对象都是指针
		// （非指针聚合为tinySize分配），
		// initSpan为我们设置指针位。这里没什么可做的。
		if doubleCheck {
			h := heapBitsForAddr(x)
			if !h.isPointer() {
				throw("heapBitsSetType: pointer bit missing")
			}
			if !h.morePointers() {
				throw("heapBitsSetType: scan bit missing")
			}
		}
		return
	}

	h := heapBitsForAddr(x)
	ptrmask := typ.gcdata // 开始1位指针掩码（或GC程序，如下处理）

	// 2字对象只有4位位图，3字对象只有6位位图。
	// 因此，这些对象与它们旁边的对象共享一个堆位图字节。
	// 这些被称为特例，主要是为了让下面的代码可以假设所有
	// 对象的长度至少为4个单词，并且它们的位图从位图字节的开头
	// 开始，或者从中间开始（分别为0和2的h移位）。

	if size == 2*goarch.PtrSize {
		if typ.size == goarch.PtrSize {
			// 我们正在分配一个足够容纳两个指针的块。在64位上，这意味着实际对象必须是两个指针，否则我们将使用一个指针大小的块。
			// 在32位上，这是最小的8字节块。
			// 所以我们可能分配了一个指针，这是
			// 这是可用的最小块。通过检查数据大小来区分。
			// （通常，正在分配的类型实例数为
			// dataSize/typ.size。）
			if goarch.PtrSize == 4 && dataSize == goarch.PtrSize {
				// 1指针对象。在32位机器上，清除
				// 未使用的第二个字的位。
				*h.bitp &^= (bitPointer | bitScan | (bitPointer|bitScan)<<heapBitsShift) << h.shift
				*h.bitp |= (bitPointer | bitScan) << h.shift
			} else {
				// 指针的2元素数组。
				*h.bitp |= (bitPointer | bitScan | (bitPointer|bitScan)<<heapBitsShift) << h.shift
			}
			return
		}
		// 其他类型。尺寸必须为2*goarch。PtrSize、
		// 和typ。kind&kindGCProg==0。
		if doubleCheck {
			if typ.size != 2*goarch.PtrSize || typ.kind&kindGCProg != 0 {
				print("runtime: heapBitsSetType size=", size, " but typ.size=", typ.size, " gcprog=", typ.kind&kindGCProg != 0, "\n")
				throw("heapBitsSetType")
			}
		}
		b := uint32(*ptrmask)
		hb := b & 3
		hb |= bitScanAll & ((bitScan << (typ.ptrdata / goarch.PtrSize)) - 1)
		// 清除此对象的位，以便设置
		// 合适的。
		*h.bitp &^= (bitPointer | bitScan | ((bitPointer | bitScan) << heapBitsShift)) << h.shift
		*h.bitp |= uint8(hb << h.shift)
		return
	} else if size == 3*goarch.PtrSize {
		b := uint8(*ptrmask)
		if doubleCheck {
			if b == 0 {
				println("runtime: invalid type ", typ.string())
				throw("heapBitsSetType: called with non-pointer type")
			}
			if goarch.PtrSize != 8 {
				throw("heapBitsSetType: unexpected 3 pointer wide size class on 32 bit")
			}
			if typ.kind&kindGCProg != 0 {
				throw("heapBitsSetType: unexpected GC prog for 3 pointer wide size class")
			}
			if typ.size == 2*goarch.PtrSize {
				print("runtime: heapBitsSetType size=", size, " but typ.size=", typ.size, "\n")
				throw("heapBitsSetType: inconsistent object sizes")
			}
		}
		if typ.size == goarch.PtrSize {
			// 该类型包含指针，否则就不会调用HeapBitSetType。
			// 由于该类型只有1个指针宽，并且包含一个指针，因此其gcdata必须正好为1。
			if doubleCheck && *typ.gcdata != 1 {
				print("runtime: heapBitsSetType size=", size, " typ.size=", typ.size, "but *typ.gcdata", *typ.gcdata, "\n")
				throw("heapBitsSetType: unexpected gcdata for 1 pointer wide type size in 3 pointer wide size class")
			}
			// 3元素指针数组。将ptrmask展开3次，得到00000111。
			b = 7
		}

		hb := b & 7
		// 为所有指针设置位扫描位。
		hb |= hb << wordsPerBitmapByte
		// 始终设置第一个位扫描位，因为该类型包含指针。
		hb |= bitScan
		// 如果设置了第三个位扫描位，则还需要设置第二个位扫描位。
		hb |= hb & (bitScan << (2 * heapBitsShift)) >> 1

		// 对于h.shift>1的堆位，需要将部分
		// 写入h.bitp，并将部分写入下一个h.bitp。
		switch h.shift {
		case 0:
			*h.bitp &^= mask3 << 0
			*h.bitp |= hb << 0
		case 1:
			*h.bitp &^= mask3 << 1
			*h.bitp |= hb << 1
		case 2:
			*h.bitp &^= mask2 << 2
			*h.bitp |= (hb & mask2) << 2
			// 写入第一个字节的两个字。
			// 前进两个单词以进入下一个字节。
			h = h.next().next()
			*h.bitp &^= mask1
			*h.bitp |= (hb >> 2) & mask1
		case 3:
			*h.bitp &^= mask1 << 3
			*h.bitp |= (hb & mask1) << 3
			// 一个字写入第一个字节。
			// 前进一个字到下一个字节。
			h = h.next()
			*h.bitp &^= mask2
			*h.bitp |= (hb >> 1) & mask2
		}
		return
	}

	// 将1位ptrmask复制到2位位图。
	// 基本方法是使用单个uintptr作为位缓冲区，
	// 在重新加载缓冲区和写入位图字节之间交替使用。
	// 通常，一个加载可以提供两个位图字节写入。
	// 这是很多行代码，但它编译成的机器指令相对较少。

	outOfPlace := false
	if arenaIndex(x+size-1) != arenaIdx(h.arena) || (doubleCheck && fastrandn(2) == 0) {
		// 此对象跨越堆区域，因此位图可能是
		// 不连续的。将其展开到对象中，而不是
		// 然后将其复制出来。
		// 
		// 在双重检查模式下，我们仍然随机执行此操作，以
		// 对位图复制路径进行压力测试。
		outOfPlace = true
		h.bitp = (*uint8)(unsafe.Pointer(x))
		h.last = nil
	}

	var (
		// ptrmak输入。
		p     *byte   // 最后一个ptrmask字节读取
		b     uintptr // ptrmask位已加载
		nb    uintptr // 下一次读取时b中的位数
		endp  *byte   // 要读取（然后重复）的最后一个ptrmask字节
		endnb uintptr // endp 

		// 堆位图输出。
		w     uintptr // 已处理的字数
		nw    uintptr // 要处理的字数
		hbitp *byte   // 要写入的下一个堆位图字节
		hb    uintptr // 正在为*hbitp 
	)

	hbitp = h.bitp

	// 处理GC程序准备的位。延迟到代码
	// 的这一部分，这样我们就可以使用与1位大小写相同的双重检查机制
	// 了。以上任何内容都不可能遇到
	// GC程序：这些案例都太小了。
	if typ.kind&kindGCProg != 0 {
		heapBitsSetTypeGCProg(h, typ.ptrdata, typ.size, dataSize, size, addb(typ.gcdata, 4))
		if doubleCheck {
			// 通过运行GC程序创建1位指针掩码
			// 然后跳转到下面的双重检查代码，对GC程序写入的堆位进行双重检查。
			// 这不会捕获1位和4位
			// GC程序执行之间共享的错误，但它会捕获特定于这些错误之一的错误，以及HeapBitsetTypeGCprog的
			// 数组实现中的错误。
			lock(&debugPtrmask.lock)
			if debugPtrmask.data == nil {
				debugPtrmask.data = (*byte)(persistentalloc(1<<20, 1, &memstats.other_sys))
			}
			ptrmask = debugPtrmask.data
			runGCProg(addb(typ.gcdata, 4), nil, ptrmask, 1)
		}
		goto Phase4
	}

	// 注意尺寸：
	// 
	// 典型。size是对象中的字数，
	// 和typ。ptrdata是包含指针的对象的前缀
	// 中的字数。也就是说，最后的
	// 类型。大小-典型。ptrdata单词不包含指针。
	// 这允许优化常见模式，其中
	// 一个对象有一个小的头，后面跟着一个大的标量
	// 缓冲区。如果我们知道指针结束了，我们就根本没有
	// 来扫描缓冲区的堆位图。
	// 1位PTR磁盘的大小被调整为只包含
	// 类型的位。ptrdata前缀，零填充为位图的完整字节
	// 。这段代码设置nw（如下），这样堆位图
	// 位只针对类型写入。ptrdata前缀；如果分配的对象中有更多的空间，则下一个堆位图
	// 条目为00，表示没有更多的指针
	// 要扫描。因此，只需要ptrdata字节的ptrmak。
	// 
	// 复制副本没有那么好：如果有一个带有标量尾部的
	// 对象数组，除了最后一个尾部之外，所有的尾部都必须初始化，因为没有办法说“向前跳过”。
	// 然而，由于
	// 的重复类型可能不是4个指针的倍数（一个堆位图字节），
	// 代码必须处理最后一个ptrmak字节，特别是
	// 将其视为只包含endnb指针的位，
	// 其中endnb<=4。我们表示大的标量尾部，必须通过将endnb设置为大于4来在复制中扩展。
	// 这将产生从b中读取许多位的效果，
	// 但一旦实际位被移出，b将提供我们试图读取的尽可能多的
	// 零位，这正是我们所需要的。

	p = ptrmask
	if typ.size < dataSize {
		// 为一个类型数组填充位。
		// 设置在主循环期间重复PTR任务。
		// 请注意，ptrmak只描述了
		const maxBits = goarch.PtrSize*8 - 7
		if typ.ptrdata/goarch.PtrSize <= maxBits {
			// 整个ptrmak可以放在uintptpr中，并且可以容纳一个字节片段。
			// 加载到pbits中，再也不读取ptrmask。
			// 当PTR任务中的
			// 少于8位时，这一点尤为重要；否则，在中间ABCFDG 

			// 将ptrmak累积到b中。
			// ptrmak的大小仅用于描述类型。ptrdata，但我们将
			// 记录为描述类型。大小字节，因为所有高位都是零。
			nb = typ.ptrdata / goarch.PtrSize
			for i := uintptr(0); i < nb; i += 8 {
				b |= uintptr(*p) << i
				p = add1(p)
			}
			nb = typ.size / goarch.PtrSize

			// 复制ptrmask以填充整个pbits uintptr。
			// 加倍和截断的步骤比
			// 每次按nb迭代的步骤要少。（nb可能是1。）
			// 因为我们加载了类型。ptrdata/goarch。PtrSize位
			// 但它们假装具有类型。大小/目标。PtrSize，
			// 可能没有必要/可能进行复制。
			pbits = b
			endnb = nb
			if nb+nb <= maxBits {
				for endnb <= goarch.PtrSize*8 {
					pbits |= pbits << endnb
					endnb += endnb
				}
				// 截断为原始ptrmak的倍数。
				// 因为nb+nb<=maxBits，所以nb适合一个字节。
				// 字节除法比uintptr除法便宜。
				endnb = uintptr(maxBits/byte(nb)) * nb
				pbits &= 1<<endnb - 1
				b = pbits
				nb = endnb
			}

			// 清除p和endp作为使用pbits的哨兵。
			// 在第二阶段回路中检查。
			p = nil
			endp = nil
		} else {
			// ptrmak更大。多读几遍。
			n := (typ.ptrdata/goarch.PtrSize+7)/8 - 1
			endp = addb(ptrmask, n)
			endnb = typ.size/goarch.PtrSize - n*8
		}
	}
	if p != nil {
		b = uintptr(*p)
		p = add1(p)
		nb = 8
	}

	if typ.size == dataSize {
		// 单个条目：一旦到达非指针数据，就可以停止。
		nw = typ.ptrdata / goarch.PtrSize
	} else {
		// 数组中重复出现的typ实例。
		// 必须完整处理前N-1个条目，但一旦我们到达最后一个条目中的非指针数据，就可以停止
		// 了。
		nw = ((dataSize/typ.size-1)*typ.size + typ.ptrdata) / goarch.PtrSize
	}
	if nw == 0 {
		// 没有指针！打电话的人应该检查一下。
		println("runtime: invalid type ", typ.string())
		throw("heapBitsSetType: called with non-pointer type")
		return
	}

	// 第一阶段：前导字节（移位=0）或半字节（移位=2）的特殊情况。
	// 前导字节是特殊的，因为它包含未设置扫描位的字1、
	// 的位。
	// 前导半字节是特殊的，因为它是半字节，所以我们必须小心已经存在的位。
	switch {
	default:
		throw("heapBitsSetType: unexpected shift")

	case h.shift == 0:
		// ptrmak和堆位图对齐。
		// 
		// 这是一条适用于小对象的快速路径。
		// 
		// 我们写出的第一个字节包括对象的前四个
		// 字。第一个
		// 字上的扫描/死位必须设置为扫描，因为对象中的某个地方有指针
		// 。
		// 在下面的所有文字中，我们适当地设置了scan/dead 
		// 以指示对象继续
		// 到位图中的下一个2位条目。
		// 
		// 我们在这里一次设置四个位，但是如果对象
		// 少于四个字，第三阶段将清除
		// 不必要的位。
		hb = b & bitPointerAll
		hb |= bitScanAll
		if w += 4; w >= nw {
			goto Phase3
		}
		*hbitp = uint8(hb)
		hbitp = add1(hbitp)
		b >>= 4
		nb -= 4

	case h.shift == 2:
		// ptrmak和堆位图未对齐。
		// 
		// 在32位体系结构上，只有对应于24字节大小类的6字对象才能以h.shift 2开头，因为
		// 所有其他非16字节对齐的大小类都已由
		// 在32位上heapBitsSetType开头的特殊代码路径处理。
		// 
		// 许多大小的类只有16字节对齐。在64位体系结构上
		// 这会导致堆位图位置从h.shift为2开始。
		// 
		// 前两个字的位位于与另一个对象共享的字节
		// 中，因此我们必须小心已经存在的位
		// 中。
		// 
		// 我们处理了上面的1字、2字和3字对象，
		// 所以这至少是一个6字对象。
		hb = (b & (bitPointer | bitPointer<<heapBitsShift)) << (2 * heapBitsShift)
		hb |= bitScan << (2 * heapBitsShift)
		if nw > 1 {
			hb |= bitScan << (3 * heapBitsShift)
		}
		b >>= 2
		nb -= 2
		*hbitp &^= uint8((bitPointer | bitScan | ((bitPointer | bitScan) << heapBitsShift)) << (2 * heapBitsShift))
		*hbitp |= uint8(hb)
		hbitp = add1(hbitp)
		if w += 2; w >= nw {
			// 我们知道有更多的数据，因为我们处理了上面的2字和3字对象。
			// 这必须至少是一个6字的对象。如果指针字用完了，请在下一个位图字节中标记no scan并完成。
			hb = 0
			w += 4
			goto Phase3
		}
	}

	// 第2阶段：位图中的完整字节，最多但不包括写入位图中最后一个字节（完整或部分）。
	// 循环计算最后一次写入的位，但不执行写入；
	// 它在hb中保留位，以便在第3阶段进行处理。
	// 为了避免重复调整nb，我们减去了我们将要在循环的前半部分使用的4位
	// 然后我们只显式地调整nb 
	// 如果每个迭代使用的8位没有被加载在循环中间的8位平衡。
	nb -= 4
	for {
		// 发出位图字节。
		// b至少有nb+4位，但有一个例外：
		// 如果w+4>=nw，则b只有nw-w位，
		// 但我们将在中断处停止，然后在第3阶段适当截断
		// 位。
		hb = b & bitPointerAll
		hb |= bitScanAll
		if w += 4; w >= nw {
			break
		}
		*hbitp = uint8(hb)
		hbitp = add1(hbitp)
		b >>= 4

		// 加载更多位。b现在有nb。
		if p != endp {
			// 快速路径：继续从ptrmak读取。
			// nb未修改：我们刚刚加载了8位，
			// 下一次迭代将消耗8位，
			// 下次我们来到这里时，我们将得到相同的nb。
			if nb < 8 {
				b |= uintptr(*p) << nb
				p = add1(p)
			} else {
				// 减少b中的位数。
				// 如果我们跳过标量尾部上的
				// 这一点很重要，因为nb可能会大于b的位宽度。
				nb -= 8
			}
		} else if p == nil {
			// 几乎同样快的路径：跟踪位计数并从pbits重新填充。
			// 用于短时间重复。
			if nb < 8 {
				b |= pbits << nb
				nb += endnb
			}
			nb -= 8 // 下一次迭代
		} else {
			// 慢路径：到达PTR任务的末尾。
			// 处理最后的部分字节并倒带开始。
			b |= uintptr(*p) << nb
			nb += endnb
			if nb < 8 {
				b |= uintptr(*ptrmask) << nb
				p = add1(ptrmask)
			} else {
				nb -= 8
				p = ptrmask
			}
		}

		// 发出位图字节。
		hb = b & bitPointerAll
		hb |= bitScanAll
		if w += 4; w >= nw {
			break
		}
		*hbitp = uint8(hb)
		hbitp = add1(hbitp)
		b >>= 4
	}

Phase3:
	// 第3阶段：写入最后一个字节或部分字节，并将剩余的位图项置零。
	if w > nw {
		// 计算hb中尚未写入内存的4个条目，
		// 有比可能的指针槽更多的条目。
		// 丢弃多余的条目（不能超过3个）。
		mask := uintptr(1)<<(4-(w-nw)) - 1
		hb &= mask | mask<<4 // 将掩码同时应用于指针位和扫描位
	}

	// 将nw从计算可能的指针字更改为分配中的总字。
	nw = size / goarch.PtrSize

	// 写入整个位图字节。
	// 第一个是血红蛋白，其余为零。
	if w <= nw {
		*hbitp = uint8(hb)
		hbitp = add1(hbitp)
		hb = 0 // 对于可能的最后半字节，请参见
		for w += 4; w <= nw; w += 4 {
			*hbitp = 0
			hbitp = add1(hbitp)
		}
	}

	// 如果有，请写入最后的部分位图字节。
	// 我们知道w>nw，否则我们仍然在上面的循环中。
	// 只有在hb中有4个条目，它才能更大。
	// 如果w==nw+4，那么就没有什么可做的了：我们编写了所有nw条目
	// 并且可以丢弃hb中的4。
	// 但是如果w==nw+2，我们需要在hb中写前两个。
	// 该字节与下一个对象共享，因此请小心使用现有位。
	if w == nw+2 {
		*hbitp = *hbitp&^(bitPointer|bitScan|(bitPointer|bitScan)<<heapBitsShift) | uint8(hb)
	}

Phase4:
	// 第4阶段：如有必要，将展开的位图复制到每个竞技场的位图。
	if outOfPlace {
		// TODO:我们可能可以通过
		// 特别处理[x+数据大小，x+大小）来加快这个过程。
		h := heapBitsForAddr(x)
		// cnw是剩余的堆字数或位对
		// 如上图所示.
		cnw := size / goarch.PtrSize
		src := (*uint8)(unsafe.Pointer(x))
		// 我们知道位图的第一个字节和最后一个字节是
		// 不一样，但是对于小的
		// 对象仍然可能跨越竞技场，因此它可能与相邻对象共享位图字节
		// 。
		// 
		// 如果第一个字节是共享的，则专门处理它。请参见
		// 第一阶段了解为什么这是我们需要的唯一特殊情况。
		if doubleCheck {
			if !(h.shift == 0 || h.shift == 2) {
				print("x=", x, " size=", size, " cnw=", h.shift, "\n")
				throw("bad start shift")
			}
		}
		if h.shift == 2 {
			*h.bitp = *h.bitp&^((bitPointer|bitScan|(bitPointer|bitScan)<<heapBitsShift)<<(2*heapBitsShift)) | *src
			h = h.next().next()
			cnw -= 2
			src = addb(src, 1)
		}
		// 我们现在是字节对齐的。复制到per arena 
		// 位图，直到最后一个字节（也可能是
		// 部分）。
		for cnw >= 4 {
			// 这个循环一次处理四个单词，
			// 所以相应地将cnw四舍五入。
			hNext, words := h.forwardOrBoundary(cnw / 4 * 4)

			// n是要复制的位图字节数。
			n := words / 4
			memmove(unsafe.Pointer(h.bitp), unsafe.Pointer(src), n)
			cnw -= words
			h = hNext
			src = addb(src, n)
		}
		if doubleCheck && h.shift != 0 {
			print("cnw=", cnw, " h.shift=", h.shift, "\n")
			throw("bad shift after block copy")
		}
		// 如果最后一个字节是共享的，请处理它。
		if cnw == 2 {
			*h.bitp = *h.bitp&^(bitPointer|bitScan|(bitPointer|bitScan)<<heapBitsShift) | *src
			src = addb(src, 1)
			h = h.next().next()
		}
		if doubleCheck {
			if uintptr(unsafe.Pointer(src)) > x+size {
				throw("copy exceeded object size")
			}
			if !(cnw == 0 || cnw == 2) {
				print("x=", x, " size=", size, " cnw=", cnw, "\n")
				throw("bad number of remaining words")
			}
			// 设置hbitp，以便下面的双重检查代码可以检查它。
			hbitp = h.bitp
		}
		// 将我们编写位图的对象归零。
		memclrNoHeapPointers(unsafe.Pointer(x), uintptr(unsafe.Pointer(src))-x)
	}

	// 仔细检查整个位图。
	if doubleCheck {
		// x+大小可能不会指向堆，因此请备份一个
		// word，然后按照上面的方式进行升级。
		end := heapBitsForAddr(x + size - goarch.PtrSize)
		if outOfPlace {
			// 在不适当的复制中，我们只需使用next来改进
			// 即可。
			end = end.next()
		} else {
			// 不要使用next，因为这可能会升级到
			// 下一个竞技场，而现有的逻辑
			// 不会这样做。
			end.shift += heapBitsShift
			if end.shift == 4*heapBitsShift {
				end.bitp, end.shift = add1(end.bitp), 0
			}
		}
		if typ.kind&kindGCProg == 0 && (hbitp != end.bitp || (w == nw+2) != (end.shift == 2)) {
			println("ended at wrong bitmap byte for", typ.string(), "x", dataSize/typ.size)
			print("typ.size=", typ.size, " typ.ptrdata=", typ.ptrdata, " dataSize=", dataSize, " size=", size, "\n")
			print("w=", w, " nw=", nw, " b=", hex(b), " nb=", nb, " hb=", hex(hb), "\n")
			h0 := heapBitsForAddr(x)
			print("initial bits h0.bitp=", h0.bitp, " h0.shift=", h0.shift, "\n")
			print("ended at hbitp=", hbitp, " but next starts at bitp=", end.bitp, " shift=", end.shift, "\n")
			throw("bad heapBitsSetType")
		}

		// 仔细检查要写入的位是否正确写入。不幸的是，
		// 没有检查其他位是否被写入。
		h := heapBitsForAddr(x)
		nptr := typ.ptrdata / goarch.PtrSize
		ndata := typ.size / goarch.PtrSize
		count := dataSize / typ.size
		totalptr := ((count-1)*typ.size + typ.ptrdata) / goarch.PtrSize
		for i := uintptr(0); i < size/goarch.PtrSize; i++ {
			j := i % ndata
			var have, want uint8
			have = (*h.bitp >> h.shift) & (bitPointer | bitScan)
			if i >= totalptr {
				if typ.kind&kindGCProg != 0 && i < (totalptr+3)/4*4 {
					// HeapBitSetTypeGCProg始终在位扫描的全半字节中填充
					// 。
					want = bitScan
				}
			} else {
				if j < nptr && (*addb(ptrmask, j/8)>>(j%8))&1 != 0 {
					want |= bitPointer
				}
				want |= bitScan
			}
			if have != want {
				println("mismatch writing bits for", typ.string(), "x", dataSize/typ.size)
				print("typ.size=", typ.size, " typ.ptrdata=", typ.ptrdata, " dataSize=", dataSize, " size=", size, "\n")
				print("kindGCProg=", typ.kind&kindGCProg != 0, " outOfPlace=", outOfPlace, "\n")
				print("w=", w, " nw=", nw, " b=", hex(b), " nb=", nb, " hb=", hex(hb), "\n")
				h0 := heapBitsForAddr(x)
				print("initial bits h0.bitp=", h0.bitp, " h0.shift=", h0.shift, "\n")
				print("current bits h.bitp=", h.bitp, " h.shift=", h.shift, " *h.bitp=", hex(*h.bitp), "\n")
				print("ptrmask=", ptrmask, " p=", p, " endp=", endp, " endnb=", endnb, " pbits=", hex(pbits), " b=", hex(b), " nb=", nb, "\n")
				println("at word", i, "offset", i*goarch.PtrSize, "have", hex(have), "want", hex(want))
				if typ.kind&kindGCProg != 0 {
					println("GC program:")
					dumpGCProg(addb(typ.gcdata, 4))
				}
				throw("bad heapBitsSetType")
			}
			h = h.next()
		}
		if ptrmask == debugPtrmask.data {
			unlock(&debugPtrmask.lock)
		}
	}
}

var debugPtrmask struct {
	lock mutex
	data *byte
}

// heapBitsSetTypeGCProg使用GC程序实现heapBitsSetType。
// progSize是程序描述的内存大小。
// elemSize是GC程序描述的元素的大小（前缀）。
// 数据大小是预期数据的总大小，是elemSize的倍数。
// allocSize是分配内存的总大小。
// 
// GC程序仅用于大型分配。
// heapBitsSetType要求allocSize是4个字的倍数，
// 这样相关的位图字节就不会与周围的
// 对象共享。
func heapBitsSetTypeGCProg(h heapBits, progSize, elemSize, dataSize, allocSize uintptr, prog *byte) {
	if goarch.PtrSize == 8 && allocSize%(4*goarch.PtrSize) != 0 {
		// 对齐将是错误的。
		throw("heapBitsSetTypeGCProg: small allocation")
	}
	var totalBits uintptr
	if elemSize == dataSize {
		totalBits = runGCProg(prog, nil, h.bitp, 2)
		if totalBits*goarch.PtrSize != progSize {
			println("runtime: heapBitsSetTypeGCProg: total bits", totalBits, "but progSize", progSize)
			throw("heapBitsSetTypeGCProg: unexpected bit count")
		}
	} else {
		count := dataSize / elemSize

		// 拼凑程序预告片，在执行以下操作的程序后运行：
		// 文字（0）
		// 重复（1，elemSize-progSize-1）
		// 重复（elemSize，count-1）
		// 此零填充第一个元素中剩余的数据，然后
		// 重复第一个元素以填充数组。
		var trailer [40]byte // 3个变量（每个变量最多10个）+一些字节
		i := 0
		if n := elemSize/goarch.PtrSize - progSize/goarch.PtrSize; n > 0 {
			// literal（0）
			trailer[i] = 0x01
			i++
			trailer[i] = 0
			i++
			if n > 1 {
				// repeat（1，n-1）
				trailer[i] = 0x81
				i++
				n--
				for ; n >= 0x80; n >>= 7 {
					trailer[i] = byte(n | 0x80)
					i++
				}
				trailer[i] = byte(n)
				i++
			}
		}
		// repeat（elemSize/ptrSize，count-1）
		trailer[i] = 0x80
		i++
		n := elemSize / goarch.PtrSize
		for ; n >= 0x80; n >>= 7 {
			trailer[i] = byte(n | 0x80)
			i++
		}
		trailer[i] = byte(n)
		i++
		n = count - 1
		for ; n >= 0x80; n >>= 7 {
			trailer[i] = byte(n | 0x80)
			i++
		}
		trailer[i] = byte(n)
		i++
		trailer[i] = 0
		i++

		runGCProg(prog, &trailer[0], h.bitp, 2)

		// 尽管我们刚刚填写了完整的数组，
		// 记录我们只填写了
		// 最后一个元素的PTR数据。这将导致下面的代码
		// memclr最后一个数组元素的死段
		// ，这样scanobject就可以在最后一个元素的早期停止。
		totalBits = (elemSize*(count-1) + progSize) / goarch.PtrSize
	}
	endProg := unsafe.Pointer(addb(h.bitp, (totalBits+3)/4))
	endAlloc := unsafe.Pointer(addb(h.bitp, allocSize/goarch.PtrSize/wordsPerBitmapByte))
	memclrNoHeapPointers(endProg, uintptr(endAlloc)-uintptr(endProg))
}

// progToPointerMask返回GC程序prog输出的1位指针掩码。
// size prog描述的区域大小，以字节为单位。
// 结果位向量的大小不超过size/goarch。p将位变小。
func progToPointerMask(prog *byte, size uintptr) bitvector {
	n := (size/goarch.PtrSize + 7) / 8
	x := (*[1 << 30]byte)(persistentalloc(n+1, 1, &memstats.buckhash_sys))[:n+1]
	x[len(x)-1] = 0xa1 // 溢出检查哨兵
	n = runGCProg(prog, nil, &x[0], 1)
	if x[len(x)-1] != 0xa1 {
		throw("progToPointerMask: overflow")
	}
	return bitvector{int32(n), &x[0]}
}

// 压缩GC指针位图，又名GC程序。
// 
// 对于包含数组的大型类型，类型信息具有
// 自然重复，可以对其进行编码，以节省
// 二进制文件和类型信息的内存表示中的空间。
// 
// 编码是一个简单的Lempel-Ziv式字节码机
// 带有以下说明：
// 
// 00000000:停止
// 0nnnnn:从下一个（n+7）/8字节复制n位
// 1000000 n c:重复之前的n位c次；n、 c是变量
// 1nnnnnnn c：重复前面的n位c次；c是一个变量

// runGCProg执行GC程序prog，如果非nil，则
// 使用给定大小的条目写入dst。
// 如果大小=1，则dst是从dst向前放置的1位指针掩码。
// 如果大小=2，则dst是2位堆位图，并从dst开始向后写入
// 因为堆位图是这样的。在这种情况下，调用方保证只需写入dst中的整个字节。
// 
// runGCProg返回写入内存的1位或2位条目数。
func runGCProg(prog, trailer, dst *byte, size int) uintptr {
	dstStart := dst

	// 等待写入内存的位。
	var bits uintptr
	var nbits uintptr

	p := prog
Run:
	for {
		// 刷新累积的完整字节。
		// 循环的其余部分假设nbits<=7。
		for ; nbits >= 8; nbits -= 8 {
			if size == 1 {
				*dst = uint8(bits)
				dst = add1(dst)
				bits >>= 8
			} else {
				v := bits&bitPointerAll | bitScanAll
				*dst = uint8(v)
				dst = add1(dst)
				bits >>= 4
				v = bits&bitPointerAll | bitScanAll
				*dst = uint8(v)
				dst = add1(dst)
				bits >>= 4
			}
		}

		// 处理一条指令。
		inst := uintptr(*p)
		p = add1(p)
		n := inst & 0x7F
		if inst&0x80 == 0 {
			// 文字位；n==0表示程序结束。
			if n == 0 {
				// 项目结束；如果存在，继续在拖车中。重复一遍。如果n==0，则在下一个字节中以变量进行编码。
				if trailer != nil {
					p = trailer
					trailer = nil
					continue
				}
				break Run
			}
			nbyte := n / 8
			for i := uintptr(0); i < nbyte; i++ {
				bits |= uintptr(*p) << nbits
				p = add1(p)
				if size == 1 {
					*dst = uint8(bits)
					dst = add1(dst)
					bits >>= 8
				} else {
					v := bits&0xf | bitScanAll
					*dst = uint8(v)
					dst = add1(dst)
					bits >>= 4
					v = bits&0xf | bitScanAll
					*dst = uint8(v)
					dst = add1(dst)
					bits >>= 4
				}
			}
			if n %= 8; n > 0 {
				bits |= uintptr(*p) << nbits
				p = add1(p)
				nbits += n
			}
			continue Run
		}

		if n == 0 {
			for off := uint(0); ; off += 7 {
				x := uintptr(*p)
				p = add1(p)
				n |= (x & 0x7F) << off
				if x&0x80 == 0 {
					break
				}
			}
		}

		// 计数在下一个字节中以变量编码。
		c := uintptr(0)
		for off := uint(0); ; off += 7 {
			x := uintptr(*p)
			p = add1(p)
			c |= (x & 0x7F) << off
			if x&0x80 == 0 {
				break
			}
		}
		c *= n // 现在要复制的位总数

		// 如果重复的位数很小，请将它们加载到寄存器中，并将该寄存器用于整个循环
		// 而不是重复从内存中读取。
		// 这里处理少于8位的数据使一般循环更简单。
		// 截止日期是goarch。PtrSize*8-7以保证当我们将
		// 模式添加到最多包含7位（一个部分字节）的位缓冲区
		// 时不会溢出。
		src := dst
		const maxBits = goarch.PtrSize*8 - 7
		if n <= maxBits {
			// 从输出缓冲区中的位开始。
			pattern := bits
			npattern := nbits

			// 如果我们需要更多的位，请从内存中获取它们。
			if size == 1 {
				src = subtract1(src)
				for npattern < n {
					pattern <<= 8
					pattern |= uintptr(*src)
					src = subtract1(src)
					npattern += 8
				}
			} else {
				src = subtract1(src)
				for npattern < n {
					pattern <<= 4
					pattern |= uintptr(*src) & 0xf
					src = subtract1(src)
					npattern += 4
				}
			}

			// 我们从整个位输出缓冲区开始，然后从整个字节加载位。
			// 不管怎样，我们现在可能有太多而不是太少。
			// 丢弃多余的。
			if npattern > n {
				pattern >>= npattern - n
				npattern = n
			}

			// 最多将模式复制到最大位。
			if npattern == 1 {
				// 重复一位。
				// 如果位为1，则使图案全部为1。
				// 如果位为0，则模式已经全部为0，
				// 但我们可以声称，字中的位数
				// 等于我们需要的数字（c），
				// 因为位的右移将填充零。
				if pattern == 1 {
					pattern = 1<<maxBits - 1
					npattern = maxBits
				} else {
					npattern = c
				}
			} else {
				b := pattern
				nb := npattern
				if nb+nb <= maxBits {
					// 双模式，直到整个uintptr填满。
					for nb <= goarch.PtrSize*8 {
						b |= b << nb
						nb += nb
					}
					// 用高位删除原始图案的不完整副本。
					// TODO（rsc）：在没有除法的系统上替换为表查找或循环？
					nb = maxBits / npattern * npattern
					b &= 1<<nb - 1
					pattern = b
					npattern = nb
				}
			}

			// 将模式添加到位缓冲区并刷新位缓冲区，c/N模式次数。
			// 因为模式包含>8位，所以每次迭代都会有完整的字节来刷新
			// 。
			for ; c >= npattern; c -= npattern {
				bits |= pattern << nbits
				nbits += npattern
				if size == 1 {
					for nbits >= 8 {
						*dst = uint8(bits)
						dst = add1(dst)
						bits >>= 8
						nbits -= 8
					}
				} else {
					for nbits >= 4 {
						*dst = uint8(bits&0xf | bitScanAll)
						dst = add1(dst)
						bits >>= 4
						nbits -= 4
					}
				}
			}

			// 将最后一个片段添加到位缓冲区。
			if c > 0 {
				pattern &= 1<<c - 1
				bits |= pattern << nbits
				nbits += c
			}
			continue Run
		}

		// 重复；n太大，无法放入寄存器。
		// 由于nbits<=7，我们知道重复数据的前几个字节已经写入内存。
		off := n - nbits // n>nbits，因为n>maxBits和nbits<=7 
		if size == 1 {
			// 前导src片段。
			src = subtractb(src, (off+7)/8)
			if frag := off & 7; frag != 0 {
				bits |= uintptr(*src) >> (8 - frag) << nbits
				src = add1(src)
				nbits += frag
				c -= frag
			}
			// 主循环：加载一个字节，写入另一个字节。
			// 位在位缓冲区中旋转。
			for i := c / 8; i > 0; i-- {
				bits |= uintptr(*src) << nbits
				src = add1(src)
				*dst = uint8(bits)
				dst = add1(dst)
				bits >>= 8
			}
			// 最后的src片段。
			if c %= 8; c > 0 {
				bits |= (uintptr(*src) & (1<<c - 1)) << nbits
				nbits += c
			}
		} else {
			// 前导src片段。
			src = subtractb(src, (off+3)/4)
			if frag := off & 3; frag != 0 {
				bits |= (uintptr(*src) & 0xf) >> (4 - frag) << nbits
				src = add1(src)
				nbits += frag
				c -= frag
			}
			// 主循环：加载一个字节，写入另一个字节。
			// 位在位缓冲区中旋转。
			for i := c / 4; i > 0; i-- {
				bits |= (uintptr(*src) & 0xf) << nbits
				src = add1(src)
				*dst = uint8(bits&0xf | bitScanAll)
				dst = add1(dst)
				bits >>= 4
			}
			// 最后的src片段。
			if c %= 4; c > 0 {
				bits |= (uintptr(*src) & (1<<c - 1)) << nbits
				nbits += c
			}
		}
	}

	// 使用全字节写入，即使是最后一个字节，也要写入任何最后的位。
	var totalBits uintptr
	if size == 1 {
		totalBits = (uintptr(unsafe.Pointer(dst))-uintptr(unsafe.Pointer(dstStart)))*8 + nbits
		nbits += -nbits & 7
		for ; nbits > 0; nbits -= 8 {
			*dst = uint8(bits)
			dst = add1(dst)
			bits >>= 8
		}
	} else {
		totalBits = (uintptr(unsafe.Pointer(dst))-uintptr(unsafe.Pointer(dstStart)))*4 + nbits
		nbits += -nbits & 3
		for ; nbits > 0; nbits -= 4 {
			v := bits&0xf | bitScanAll
			*dst = uint8(v)
			dst = add1(dst)
			bits >>= 4
		}
	}
	return totalBits
}

// Materialiegcprog为大小为ptrdata的对象分配（1位）指针位掩码的空间。然后用程序prog指定的
// 指针位掩码填充该空间。
// 位掩码从s.startAddr开始。
// 结果必须通过非物质化egcprog处理。
func materializeGCProg(ptrdata uintptr, prog *byte) *mspan {
	// ptrdata的每个字都需要位图中的一位。
	bitmapBytes := divRoundUp(ptrdata, 8*goarch.PtrSize)
	// 计算bitmapBytes所需的页数。
	pages := divRoundUp(bitmapBytes, pageSize)
	s := mheap_.allocManual(pages, spanAllocPtrScalarBits)
	runGCProg(addb(prog, 4), nil, (*byte)(unsafe.Pointer(s.startAddr)), 1)
	return s
}
func dematerializeGCProg(s *mspan) {
	mheap_.freeManual(s, spanAllocPtrScalarBits)
}

func dumpGCProg(p *byte) {
	nptr := 0
	for {
		x := *p
		p = add1(p)
		if x == 0 {
			print("\t", nptr, " end\n")
			break
		}
		if x&0x80 == 0 {
			print("\t", nptr, " lit ", x, ":")
			n := int(x+7) / 8
			for i := 0; i < n; i++ {
				print(" ", hex(*p))
				p = add1(p)
			}
			print("\n")
			nptr += int(x)
		} else {
			nbit := int(x &^ 0x80)
			if nbit == 0 {
				for nb := uint(0); ; nb += 7 {
					x := *p
					p = add1(p)
					nbit |= int(x&0x7f) << nb
					if x&0x80 == 0 {
						break
					}
				}
			}
			count := 0
			for nb := uint(0); ; nb += 7 {
				x := *p
				p = add1(p)
				count |= int(x&0x7f) << nb
				if x&0x80 == 0 {
					break
				}
			}
			print("\t", nptr, " repeat ", nbit, " × ", count, "\n")
			nptr += nbit * count
		}
	}
}

// 测试。

func getgcmaskcb(frame *stkframe, ctxt unsafe.Pointer) bool {
	target := (*stkframe)(ctxt)
	if frame.sp <= target.sp && target.sp < frame.varp {
		*target = *frame
		return false
	}
	return true
}

// gcbits返回x的GC类型信息，用于测试。
// 结果是位图条目（0或1），每个字节一个条目。
// go:linkname reflect_gcbits reflect。gcbits 
func reflect_gcbits(x any) []byte {
	ret := getgcmask(x)
	typ := (*ptrtype)(unsafe.Pointer(efaceOf(&x)._type)).elem
	nptr := typ.ptrdata / goarch.PtrSize
	for uintptr(len(ret)) > nptr && ret[len(ret)-1] == 0 {
		ret = ret[:len(ret)-1]
	}
	return ret
}

// 返回存储在ep中用于测试的指针的GC类型信息。
// 如果ep指向堆栈，则只会返回静态实时信息
// （即不适用于仅为动态实时堆栈对象的对象）。
func getgcmask(ep any) (mask []byte) {
	e := *efaceOf(&ep)
	p := e.data
	t := e._type
	// 数据或bss 
	for _, datap := range activeModules() {
		// 数据
		if datap.data <= uintptr(p) && uintptr(p) < datap.edata {
			bitmap := datap.gcdatamask.bytedata
			n := (*ptrtype)(unsafe.Pointer(t)).elem.size
			mask = make([]byte, n/goarch.PtrSize)
			for i := uintptr(0); i < n; i += goarch.PtrSize {
				off := (uintptr(p) + i - datap.data) / goarch.PtrSize
				mask[i/goarch.PtrSize] = (*addb(bitmap, off/8) >> (off % 8)) & 1
			}
			return
		}

		// bss 
		if datap.bss <= uintptr(p) && uintptr(p) < datap.ebss {
			bitmap := datap.gcbssmask.bytedata
			n := (*ptrtype)(unsafe.Pointer(t)).elem.size
			mask = make([]byte, n/goarch.PtrSize)
			for i := uintptr(0); i < n; i += goarch.PtrSize {
				off := (uintptr(p) + i - datap.bss) / goarch.PtrSize
				mask[i/goarch.PtrSize] = (*addb(bitmap, off/8) >> (off % 8)) & 1
			}
			return
		}
	}

	// 堆
	if base, s, _ := findObject(uintptr(p), 0, 0); base != 0 {
		hbits := heapBitsForAddr(base)
		n := s.elemsize
		mask = make([]byte, n/goarch.PtrSize)
		for i := uintptr(0); i < n; i += goarch.PtrSize {
			if hbits.isPointer() {
				mask[i/goarch.PtrSize] = 1
			}
			if !hbits.morePointers() {
				mask = mask[:i/goarch.PtrSize]
				break
			}
			hbits = hbits.next()
		}
		return
	}

	// 堆栈
	if _g_ := getg(); _g_.m.curg.stack.lo <= uintptr(p) && uintptr(p) < _g_.m.curg.stack.hi {
		var frame stkframe
		frame.sp = uintptr(p)
		_g_ := getg()
		gentraceback(_g_.m.curg.sched.pc, _g_.m.curg.sched.sp, 0, _g_.m.curg, 0, nil, 1000, getgcmaskcb, noescape(unsafe.Pointer(&frame)), 0)
		if frame.fn.valid() {
			locals, _, _ := getStackMap(&frame, nil, false)
			if locals.n == 0 {
				return
			}
			size := uintptr(locals.n) * goarch.PtrSize
			n := (*ptrtype)(unsafe.Pointer(t)).elem.size
			mask = make([]byte, n/goarch.PtrSize)
			for i := uintptr(0); i < n; i += goarch.PtrSize {
				off := (uintptr(p) + i - frame.varp + size) / goarch.PtrSize
				mask[i/goarch.PtrSize] = locals.ptrbit(off)
			}
		}
		return
	}

	// 否则，GC不知道。
	// 可能是只读数据，比如malloc（0）。
	// 不能有指针
	return
}
